Skip to content

Add i18n support for homepage (国际化官网首页) - #31

Merged
huangyiirene merged 5 commits into
mainfrom
copilot/add-international-homepage
Jan 20, 2026
Merged

huangyiirene merged 5 commits into
mainfrom
copilot/add-international-homepage

Conversation

Copilot AI commented Jan 20, 2026 •

Copy link
Copy Markdown
Contributor

The homepage was hardcoded in English. This adds Chinese translations while leveraging the existing middleware and routing infrastructure.

Changes

Created apps/docs/lib/homepage-i18n.ts

  • TypeScript interface HomepageTranslations defining all translatable strings
  • English and Chinese translation objects
  • getHomepageTranslations(lang) selector with fallback to English

Updated apps/docs/app/[lang]/page.tsx

  • Changed to async function to extract lang from route params
  • Replaced hardcoded strings with translation variables
  • All sections now localized: hero, features, personas

Example

// Before
<h1>The ObjectStack Protocol</h1>

// After
const t = getHomepageTranslations(lang);
<h1>{t.hero.title.line1} <br/> {t.hero.title.line2}</h1>

Screenshots

English (/):
English

Chinese (/cn):
Chinese

Original prompt

国际化官网首页


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

@vercel

vercel Bot commented Jan 20, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
spec Ready Ready Preview, Comment Jan 20, 2026 3:41am

Request Review

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Copilot AI changed the title [WIP] Add international homepage for website Add i18n support for homepage (国际化官网首页) Jan 20, 2026
Copilot AI requested a review from hotlong January 20, 2026 03:12
@github-actions github-actions Bot added documentation Improvements or additions to documentation size/m labels Jan 20, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds internationalization (i18n) support to the ObjectStack homepage, enabling both English and Chinese language versions. The implementation leverages the existing Next.js i18n routing infrastructure while keeping translations maintainable through a centralized module.

Changes:

  • Created a new i18n module with TypeScript-typed translations for English and Chinese
  • Converted the homepage component to async to access route parameters for language detection
  • Replaced all hardcoded English strings with dynamic translation variables

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
apps/docs/lib/homepage-i18n.ts New translation module defining the HomepageTranslations interface and providing English/Chinese translation objects with a language selector function
apps/docs/app/[lang]/page.tsx Updated homepage to extract language from route params and use translations instead of hardcoded strings
packages/spec/src/system/manifest.test.ts Removed unused MenuItem test cases and import
packages/spec/src/data/field.test.ts Fixed extraneous closing brace

expect(() => MenuItemSchema.parse({ path: '/test' })).toThrow();
});
});
import { ManifestSchema, type ObjectStackManifest } from './manifest.zod';

Copilot AI Jan 20, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The removed MenuItemSchema tests provided validation coverage for the menu item structure. Ensure that menu item validation is adequately covered elsewhere in the test suite, or consider keeping these tests to maintain comprehensive schema validation.

Copilot uses AI. Check for mistakes.
xuyushun441-sys pushed a commit that referenced this pull request May 25, 2026
Adds entries 26-40 covering the gaps that make the helpdesk template
'pretty but not daily-usable' from an end-user perspective:

P0 additions:
- #26 No inline message composer on detail pages
- #27 No external-user portal mechanism
- #28 Attachment/file-list field UI not E2E

P1 additions:
- #29 No 'changed since last visit' indicator
- #30 Bulk operations UI unverified (escalates #17)
- #31 Rich-text editor scoped to comments only
- #32 No first-class canned response / macro
- #33 No collaboration presence indicators
- #34 No keyboard-shortcut API
- #35 No conditional SLA timer (pause on waiting_customer)
- #36 Formula fields can't reference foreign object fields

P2 additions:
- #37 No chart drill-down
- #38 No period-over-period analytics primitive
- #39 No inbound-channel abstraction (email-to-ticket etc.)
- #40 i18n translation namespace validation weak

Includes 'user-pain → platform-gap' mapping table tracing each end-user
complaint to a specific issue number.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
os-tesla pushed a commit that referenced this pull request Oct 25, 2026
…n to the viewer's language (objectui#13470) (#13487)

<!-- claude-projects-attribution: {"github_login":"os-elon-musk"} -->
_Requested by **Elon** · [project
thread](https://claude.ai/code/project/chan_01WedDzV26nniwxv7jC88B9D?thread=cmsg_01WedDzV26nniwxv7jC88B9D1XH3Stf1pdUctjJez54hAZ)_

- [x] Adds no product-specific widget, dialog, hook or string (see
AGENTS.md › Product-neutral renderer)

Fixes #13470
Clause-②: no
Metadata-protocol: none

## What changed

`ReportSchema` in `@objectstack/spec` types a report's `label` and
`description` as `I18nLabel`, so an inline locale map such as `{
'zh-CN', en }` is valid metadata, and the `/meta` read serves it
unchanged. Four places on the report route drew it raw:

- `ReportView`'s page heading (`previewReport.title ||
previewReport.label`) and the line under it
(`previewReport.description`). React refused the object as a child
(error #31) and the page was blank for every viewer, the card's repro.
- The app bar's report crumb in `AppHeader`. It handed the served label
to `reportLabel` as its fallback, and `reportLabel` returns an unmatched
fallback untouched, so the map reached JSX there too. With `ReportView`
fixed alone, the page stayed blank through this crumb (measured: the
ablation below).
- The drill drawer's title for a whole-report drill (empty group key)
fell back to `String(reportData.label)`, which reads `[object Object]`.

Each now resolves the map in the viewer's language
(`useObjectTranslation().language`) with `metadataText`, the app-shell
reader the app's report index (`useAppReportEntries`) and recent items
already name a report with. It is the spec's `resolveI18nLabel`, which
agrees limb for limb with `pickLocalized` (the resolver the
objectui#13380 fix used) and spells a miss `undefined`, so the existing
fallbacks (`'Report Viewer'`, the humanized slug, the report name) keep
working through `??`. A plain-string label or description draws exactly
as before. One changeset: `@object-ui/app-shell` patch.

The other surfaces that print a report's label or description were
checked and already resolve the map: the app's report index
(`AppReportIndexView`) and the ⌘K palette's app-report rows both read
`useAppReportEntries`, which calls `metadataText`. The palette's
navigation rows print the navigation entry's own label, not the
report's.

## Tests

New pins, both mounting the real component under a real `I18nProvider`,
each leg in `en` and `zh-CN`, with a plain-string report as the control:

-
`packages/app-shell/src/views/ReportView.inlineLocaleLabel-13470.test.tsx`:
the card's repro report, with the map on both `label` and `description`.
Legs `HEADING_MAP_RESOLVES`, `DESCRIPTION_MAP_RESOLVES`,
`DRILL_TITLE_MAP_RESOLVES`, `PLAIN_STRING_CONTROL`.
-
`packages/app-shell/src/layout/__tests__/AppHeader.reportCrumbInlineLocale-13470.test.tsx`:
the `AppHeader.dashboardReportCrumb-12273` harness with `useObjectLabel`
real. Legs `LABEL_MAP_RESOLVES`, `PLAIN_STRING_CONTROL`.

Readings, all at head `899902d`:

- The two new files: Test Files 2 passed (2), Tests 12 passed (12).
- Every test file that imports `ReportView`, `AppHeader`,
`AppReportIndexView` or `appReportIndex` (39 files, found by `git grep`
over test files), with `pnpm exec vitest run --maxWorkers=2`: Test Files
39 passed (39), Tests 323 passed (323).
- `turbo run type-check --filter=@object-ui/app-shell --concurrency=2`:
30 successful, 30 total. The package's `type-check` chains
`tsconfig.test.json`, so both new test files are type-checked.
- eslint on the four touched files: 0 errors. The 44 warnings are on
lines this PR does not change.
- `node scripts/check-changeset-presence.mjs`: pass (1 changeset for 1
released package). `pnpm check:new-line-citations`: 0 new citations.
`node scripts/check-changeset-no-major.mjs`: pass.

Narrowed, declared: the whole `packages/app-shell/` suite at
`--maxWorkers=2` printed nothing for 60 minutes on this shared container
and was stopped, so it is NOT MEASURED locally; CI's sharded `pnpm test`
runs it. The narrowed set is every test that imports a changed module.
Its blind spot: a test that mounts these components through another
import path without naming them.

Ablation, one site at a time on the committed fix. Each leg put the
pre-fix line back, ran the two new files, then restored the file with
`git checkout HEAD` and confirmed `git diff HEAD` was empty:

- heading reverted: 6 failed, 6 passed. Every map leg of the
`ReportView` file fails on `Objects are not valid as a React child
(found: object with keys {zh-CN, en})`; the control legs and the crumb
file pass.
- description reverted: 6 failed, 6 passed, the same error.
- drill title reverted: 2 failed, 10 passed. Only
`DRILL_TITLE_MAP_RESOLVES` fails, in both languages, and the drawer
reads `[object Object]`.
- crumb reverted: 2 failed, 10 passed. Only the crumb file's
`LABEL_MAP_RESOLVES` legs fail, on the same React error #31.

## Acceptance notes

- Same defect on the page route, measured, not fixed here (outside this
card's file surface): `AppHeader`'s page branch hands `pageDef.label` to
`pageLabel` as its fallback the same way, and `PageSchema.label` is
`I18nLabel` too. A throwaway probe (not committed) mounted this PR's
crumb harness at `/apps/srm/page/welcome` with that page's label set to
`{ 'zh-CN', en }`: it fails on `Objects are not valid as a React child
(found: object with keys {zh-CN, en})`, so a page with an inline-map
label blanks its route. Handed to the seat to file.
- `DatasetReportRenderer`'s `resolveText` (the heading of each block of
a `joined` report) reads only a map's `default` entry, so a block label
written as `{ 'zh-CN', en }` shows the block's name instead of either
string. Read-only observation from the code, not measured; nothing
filed.
- `ReportView` still reads `previewReport.title` before `label`.
`ReportSchema` declares no root `title`, so the arm reads a key no valid
document carries (the dashboard view retired its twin under ADR-0049).
Left as it was, now resolved like `label`; outside this card.
- The report crumb still passes through the `reports.NAME.label` bundle
lookup (`reportLabel`), while the page heading draws the served label as
given. That split predates this PR and is unchanged.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01EsHaC5nYMcJgk2Nr6fbVdG

---------

Co-authored-by: Claude <noreply@anthropic.com>
os-tesla pushed a commit that referenced this pull request Oct 25, 2026
…escription to the viewer's language (objectui#13488) (#13501)

<!-- claude-projects-attribution: {"github_login":"os-elon-musk"} -->
_Requested by **Elon** · [project
thread](https://claude.ai/code/project/chan_01WedDzV26nniwxv7jC88B9D?thread=cmsg_01WedDzV26nniwxv7jC88B9DRqbNi4iARYYtqZmwLSPhaR)_

- [x] Adds no product-specific widget, dialog, hook or string (see
AGENTS.md › Product-neutral renderer)

Fixes #13488
Clause-②: no
Metadata-protocol: none

## What changed

`PageSchema` in `@objectstack/spec` types a page's `label` and
`description` as `I18nLabel`, so an inline locale map such as `{
'zh-CN', en }` is valid metadata, and the `/meta` read serves it
unchanged. Two packages drew it raw on a page route, and React refused
the object as a child (error #31), so the route was blank for every
viewer:

- **`@object-ui/app-shell`, `AppHeader`'s page branch.** It handed the
served label to `pageLabel` as its fallback, and `pageLabel` returns an
unmatched fallback untouched, so the map became the phone bar's title.
It now resolves through `metadataText` in the viewer's language
(`useObjectTranslation().language`), the call the report branch has used
since objectui#13470, and still passes through the `pages.NAME.label`
bundle lookup. The humanized slug stays the fallback when no label
resolves.
- **`@object-ui/components`, `PageRenderer`.** The page's own implicit
heading (`schema.title ?? schema.label`), the line under it
(`schema.description`), and the description it hands a titled
`page:header` through `PageDescriptionContext` were all drawn raw. Each
now resolves through `pickLocalized` in the viewer's language, the
resolver `page:header` already uses for its own title in the same
package. With only the app bar fixed, an `app`, `home` or `utility` page
with a map label stayed blank through this heading (measured, below), so
the claim was widened to this file (claim amendment on #13488).
- **Swept, same file, same call:** `AppHeader`'s dashboard branch drew a
served dashboard label straight into the phone bar's title.
`DashboardSchema.label` is `I18nLabel` too, so it is now
`metadataText(…) ?? dashboardLabel(…)`.

A plain-string label or description draws exactly as before:
`metadataText` and `pickLocalized` both pass a string through, and an
empty one still falls to the slug or hides the description. Studio's
page previews (`SourcePageEditor`, `PagePreview`) render through the
same `PageRenderer`, so they get the fix too. One changeset:
`@object-ui/app-shell` and `@object-ui/components` patch.

## Tests

New pins, each mounting the real component under a real `I18nProvider`,
each leg in `en` and `zh-CN` reading different strings out of one map,
with plain strings as the control:

-
`packages/app-shell/src/layout/__tests__/AppHeader.pageDashboardTitleInlineLocale-13488.test.tsx`:
the `AppHeader.reportCrumbInlineLocale-13470` harness with
`useObjectLabel` real. Legs `PAGE_LABEL_MAP_RESOLVES`,
`DASHBOARD_LABEL_MAP_RESOLVES`, two `PLAIN_STRING_CONTROL`s.
-
`packages/components/src/__tests__/page-inline-locale-label-13488.test.tsx`:
an `app` page through the real `SchemaRenderer` and `PageRenderer`. Legs
`HEADING_MAP_RESOLVES`, `DESCRIPTION_MAP_RESOLVES`,
`HEADER_DESCRIPTION_MAP_RESOLVES` (titled `page:header`),
`PLAIN_STRING_CONTROL`.

Readings, final head `2c82e03` unless stated:

- The two new files: Test Files 2 passed (2), Tests 16 passed (16).
- Every test file that names `AppHeader`, `PageView`, `PageRenderer`,
`SourcePageEditor`, `PagePreview` or `pageKindNode`, plus every
`packages/components` test that names `PageRenderer`, `page:header`,
`pageType`, the page renderer module or an `app`/`home`/`utility` page
node (212 files, found by `git grep -lE` over test files), with `pnpm
exec vitest run --maxWorkers=2`: Test Files 212 passed (212), Tests 2161
passed (2161). Run at `bf37518`; the only later commit, `2c82e03`,
changes the type annotation of one new test's fixture, and both new
files were re-run at `2c82e03` above.
- `turbo run type-check --filter=@object-ui/app-shell
--filter=@object-ui/components --concurrency=2 --continue`: 31
successful, 31 total. Both packages' `type-check` chain
`tsconfig.test.json`, so the new test files are type-checked.
- eslint on the five touched files: 0 errors. Every warning is on a line
this PR does not add; the one on a changed line in `page.tsx` is the
`(schema as any).label` read the old line already had.
- `node scripts/check-changeset-presence.mjs`: pass (4 source files of 2
released packages, 1 changeset). `node
scripts/check-changeset-no-major.mjs`: pass. `pnpm
check:new-line-citations`: 0 new citations.

Narrowed, declared: the whole `packages/app-shell/` and
`packages/components/` suites were not run locally; CI's sharded `pnpm
test` runs them. The narrowed set's blind spot is a test that mounts
these components through another import path without naming them.

Probe before the fix, on main `85b6a48`, through the real
`SchemaRenderer` and `PageRenderer` (a throwaway file, not committed):
an `app` page with a map `label`, one with a map `description`, and one
with a titled `page:header` and a map `description` all failed on
`Objects are not valid as a React child (found: object with keys {zh-CN,
en})`.

Ablation, one site at a time on the committed fix, through
`scripts/ablation-replace.mjs` (anchor must hit once, blob must change,
restore proven by blob equal to `HEAD` and an empty `git diff HEAD`),
running the two new files each time:

- page branch reverted (`pageDef?.label || humanizeSlug(pageName)`): 2
failed, 14 passed. Only `PAGE_LABEL_MAP_RESOLVES` fails, in both
languages, on React error #31.
- dashboard branch reverted (`dashboardDef?.label ||
dashboardLabel(…)`): 2 failed, 14 passed. Only
`DASHBOARD_LABEL_MAP_RESOLVES` fails, on the same error.
- `PageRenderer` heading reverted (`schema.title ?? schema.label` raw):
2 failed, 14 passed. Only `HEADING_MAP_RESOLVES` fails, on the same
error.
- `PageRenderer` description reverted (raw `schema.description`): 4
failed, 12 passed. `DESCRIPTION_MAP_RESOLVES` and
`HEADER_DESCRIPTION_MAP_RESOLVES` fail, on the same error.

## Acceptance notes

- `InterfaceListPage` (an interface page's list surface) reads `typeof
page.label === 'string' ? page.label : page.name` for its heading and
its record drawer's title, and draws `page.description` only when it is
a string. A page with a map label does not blank there, but it shows the
page's machine name instead of its label, and a map description is not
shown. Read from the code on `85b6a48`, not measured; outside this
claim's file surface. Handed to the seat.
- `AppHeader`'s view branch hands `viewDef?.label || viewDef?.title` to
`viewTabLabel`, and a list view's `label` is `I18nLabel` in the spec as
well. The object page's tab bar (`ObjectView`) hands the same label to
the same function, so whether a map view label blanks a view route is a
question about `ObjectView`, not this header alone. Read from the code,
not measured; not changed here.
- The page's phone-bar title still passes the resolved label through the
`pages.NAME.label` bundle lookup, while the page's own heading draws the
served label. That split predates this PR and is unchanged, as on the
report route.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_012oUFcAgU5BgTKe6fa7RxD8

---------

Co-authored-by: Claude <noreply@anthropic.com>

This branch was successfully deployed

1 active deployment
Preview — 716b5e9c Deployed Jan 20, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants